#include <string.h>

#include <assert.h>
#include <arpa/inet.h>

#include "net_context.h"
#include "eth_impl.h"

using namespace usnet;

std::unique_ptr<EthImpl>
EthImpl::MakeUnique(
    const PPT proto,
    const EndpointAddr* laddr,
    const EndpointAddr* raddr,
    const Ifconfig* ifcfg)
{
    auto ctx = NetContext::GetContext();
    std::unique_ptr<EthImpl> eth_impl { new(std::nothrow) EthImpl { proto, laddr, raddr, ifcfg } };
    if (eth_impl == nullptr) {
        LOG_ERROR(ctx, "failed to new EthImpl instance");
        return nullptr;
    }
    LOG_DEBUG(ctx, "new EthImpl instance success");
    if (eth_impl->InitEFResource() == false) {
        LOG_ERROR(ctx, "failed to init EthResource");
        return nullptr;
    }
    LOG_DEBUG(ctx, "init EFResurce success");
    if(eth_impl->InitFameBufMgr() == false) {
        LOG_ERROR(ctx, "failed to init FrameBfMgr");
        return nullptr;
    }
    LOG_DEBUG(ctx, "init FrameBufMgr success");
    return eth_impl;
}

std::shared_ptr<FrameBufMgr> EthImpl::PrefillHeader()
{
    FrameBuf* fb { nullptr };
    LinkNode list_head { std::move(fb_mgr_->tx_fb_free_) };
    while ((fb = list_head.GetHead())) {
        fb->eth_hdr_ = (EthHeader*) fb->mem_addr_;
        auto eth_hdr = fb->eth_hdr_;
        memcpy(eth_hdr->src_ethaddr_, laddr_->eth_addr_, sizeof(laddr_->eth_addr_));
        memcpy(eth_hdr->dst_ethaddr_, raddr_->eth_addr_, sizeof(raddr_->eth_addr_));
        eth_hdr->be_protocol_ = htons(static_cast<uint16_t>(proto_));
        list_head.RemoveHead();
        fb_mgr_->tx_fb_free_.TailInsert(fb);
    }

    list_head = std::move(fb_mgr_->rx_fb_free_);
    while ((fb = list_head.GetHead())) {
        fb->eth_hdr_ = (EthHeader*) fb->mem_addr_;
        list_head.RemoveHead();
        fb_mgr_->rx_fb_free_.TailInsert(fb);
    }
    return fb_mgr_;
}
    
void EthImpl::SendFrame()
{
    auto ctx = NetContext::GetContext();
    FrameBuf* fb { nullptr };
    while ((fb = fb_mgr_->tx_fb_fill_.GetHead())) {
        uint32_t eth_pdu { MIN_ETH_PDU };
        if (proto_ == PPT::IPv4) {
            auto ip4_hdr = fb->inet_hdr_.ip4_hdr_;
            eth_pdu = ntohs(ip4_hdr->be_len_) + hdr_size_;
        }
        if (proto_ == PPT::IPv6) {
            // pass
        }
        if (proto_ == PPT::ARP) {
            // pass
        }
        if (ef_vi_transmit_init(&ef_res_.vi_, fb->dma_addr_, eth_pdu, fb->id_) != 0) {
            LOG_ERROR(ctx, "ef_vi_transmit_init() failed");
        }
        fb_mgr_->tx_fb_fill_.RemoveHead();
        fb_mgr_->tx_fb_sent_.TailInsert(fb);
        LOG_DEBUG(ctx, "ef_vi_transmit_init() success");
    }
    ef_vi_transmit_push(&ef_res_.vi_);
}

bool EthImpl::RecvFrame()
{
    assert(evtab_);
    auto ctx = NetContext::GetContext();
    int nev = ef_eventq_poll(&ef_res_.vi_, evtab_, sizeof(evnum_));
    if (nev <= 0) {
        return false;
    }
    bool ret { false };
    for (int k = 0; k < nev; ++k) {
        auto fb = fb_mgr_->fb_tab_[EF_EVENT_RX_RQ_ID(evtab_[k])];
        switch (EF_EVENT_TYPE(evtab_[k]))
        {
            /** Good data was received. */
            case EF_EVENT_TYPE_RX:
            {
                fb_mgr_->rx_fb_free_.RemoveNode(fb);
                fb_mgr_->rx_fb_fill_.TailInsert(fb);
                LOG_DEBUG(ctx, "frame received");
                ret = true;
                break;
            }
            /** Packets have been sent. */
            case EF_EVENT_TYPE_TX:
            {
                if (fb->flags_ & FB_REUSE) {
                    fb_mgr_->tx_fb_sent_.RemoveNode(fb);
                    fb_mgr_->tx_fb_free_.TailInsert(fb);
                }
                LOG_DEBUG(ctx, "frame transmitted");
                break;
            }
            /** Data received and buffer consumed, but something is wrong. */
            case EF_EVENT_TYPE_RX_DISCARD:
            {
                fb_mgr_->rx_fb_free_.RemoveNode(fb);
                fb_mgr_->rx_fb_wrng_.TailInsert(fb);
                LOG_DEBUG(ctx, "frame receiving error");
                break;
            }
            /** Transmit of packet failed. */
            case EF_EVENT_TYPE_TX_ERROR:
            {
                fb_mgr_->tx_fb_fill_.RemoveNode(fb);
                fb_mgr_->tx_fb_wrng_.TailInsert(fb);
                LOG_DEBUG(ctx, "frame transmitting error");
                break;
            }
            /** Received packet was truncated due to a lack of descriptors. */
            case EF_EVENT_TYPE_RX_NO_DESC_TRUNC:
            /** Software generated event. */
            case EF_EVENT_TYPE_SW:
            /** Event queue overflow. */
            case EF_EVENT_TYPE_OFLOW:
            /** TX timestamp event. */
            case EF_EVENT_TYPE_TX_WITH_TIMESTAMP:
            /** A batch of packets was received in a packed stream. */
            case EF_EVENT_TYPE_RX_PACKED_STREAM:
            /** A batch of packets was received on a RX event merge vi. */
            case EF_EVENT_TYPE_RX_MULTI:
            /** Packet has been transmitted via a "TX alternative". */
            case EF_EVENT_TYPE_TX_ALT:
            /** A batch of packets was received with error condition set. */
            case EF_EVENT_TYPE_RX_MULTI_DISCARD:
            /** Event queue has been forcibly halted (hotplug, reset, etc.) */
            case EF_EVENT_TYPE_RESET:
            default:
            {
                LOG_ERROR(ctx, "bad ef event type");
                return false;
            }
        }
    }
    return ret;
}

bool EthImpl::InitEFResource()
{
    auto ctx = NetContext::GetContext();
    if (ef_driver_open(&ef_res_.handle_) != 0) {
        LOG_ERROR(ctx, "ef_driver_open() failed");
        return false;
    }
    // if ((ifcfg_ == nullptr) || (ifcfg_->ifindex_ < 0)) {
    //     LOG_ERROR(ctx, "invalid network interface config");
    //     return false;
    // }
    if (ef_pd_alloc(&ef_res_.pd_, ef_res_.handle_, ifcfg_->ifindex_, EF_PD_DEFAULT) != 0) {
        LOG_ERROR(ctx, "ef_pd_alloc() failed");
        return false;
    }
    const uint32_t nr_fb = ifcfg_->txqlen_;
    if (ef_vi_alloc_from_pd(
        &ef_res_.vi_, ef_res_.handle_,
        &ef_res_.pd_, ef_res_.handle_,
        (nr_fb << 1), nr_fb, nr_fb,
        nullptr, -1, EF_VI_FLAGS_DEFAULT) < 0) {
        LOG_ERROR(ctx, "ef_vi_alloc_from_pd() failed");
        return false;
    }
    return true;
}

bool EthImpl::InitFameBufMgr()
{
    auto ctx = NetContext::GetContext();
    fb_mgr_ = FrameBufMgr::MakeShared(&ef_res_, ifcfg_->txqlen_, ifcfg_->txqlen_);
    if (fb_mgr_ == nullptr) {
        LOG_ERROR(ctx, "failed to create FrameBufMgr instance()");
        return false;
    }
    return true;
}

bool EthImpl::AddFilter(const FilterLevel fl)
{
    auto ctx = NetContext::GetContext();
    ef_filter_spec fs;
    ef_filter_spec_init(&fs, EF_FILTER_FLAG_NONE);
    switch (fl) {
        case FilterLevel::IP4_FULL:
        {
            if (ef_filter_spec_set_ip4_full(
                &fs, IPPROTO_TCP,
                laddr_->be_ip_.v4_,
                laddr_->be_port_,
                raddr_->be_ip_.v4_,
                raddr_->be_port_) != 0) {
                LOG_ERROR(ctx, "ef_filter_spec_set_ip4_local() failed");
                return false;
            }
            break;
        }
        case FilterLevel::IP4_LOCAL:
        {
            if (ef_filter_spec_set_ip4_local(
                &fs, IPPROTO_TCP,
                laddr_->be_ip_.v4_,
                laddr_->be_port_) != 0) {
                LOG_ERROR(ctx, "ef_filter_spec_set_ip4_local() failed");
                return false;
            }
            break;
        }
        case FilterLevel::IP6_FULL:
        {
            // pass
            break;
        }
        case FilterLevel::IP6_LOCAL:
        {
            // pass
            break;
        }
        case FilterLevel::ETH_FULL:
        {
            // pass
            break;
        }
        case FilterLevel::ETH_LOCAL:
        {
            if (ef_filter_spec_set_eth_local(&fs, EF_FILTER_VLAN_ID_ANY, laddr_->eth_addr_) != 0) {
                LOG_ERROR(ctx, "ef_filter_spec_set_ip4_local() failed");
                return false;
            }
            break;
        }
        default:
        {
            LOG_ERROR(ctx, "bad filter level");
            break;
        }
    }
    if (ef_vi_filter_add(
        &ef_res_.vi_,
        ef_res_.handle_,
        &fs,
        nullptr) != 0) {
        LOG_ERROR(ctx, "ef_vi_filter_add() failed");
        return false;
    }
    return true;
}